package com.qlogic.commons.utils.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import com.qlogic.commons.exceptions.BaseException;
import com.qlogic.commons.utils.strings.StringUtils;

public class ArchiveResourceFinder {
	
	public static final int STARTS_WITH = 0;
	public static final int ENDS_WITH = 1;
	public static final int CONTAINS = 2;
	
	private static final String JAR_URL_SEPARATOR = "!/";
	private static final String FILE_URL_PREFIX = "file:";
	
	
	protected ArrayList archives = new ArrayList ();
	
	protected LinkedHashMap parseResult = new LinkedHashMap ();

	public boolean isArchive(String name) {
		// @todo use a more intelligent test to identify a
		// zip-based archive (magic number?)
		String s = name.toLowerCase();
		return s.endsWith(".jar") || s.endsWith(".war") || s.endsWith(".zip")
				|| s.endsWith(".ear");
	}

	public void parse (String name)
		throws IOException {
		parse (name, CONTAINS);
	}
	
	public void parse (String name, int type)
		throws IOException {
		for (int i = 0; i < archives.size(); i++) {
			parse (getArchive (i), name, parseResult, type);
		}
	}

	protected void parse (Archive in, String name, LinkedHashMap parseResult, int type)
		throws IOException {
		ZipEntry entry;
		while ((entry = in.getZis().getNextEntry()) != null) {
			String entryName = entry.getName();
			if (isArchive(entryName)) {
				parse(new Archive (in.getJarFile(), new ZipInputStream(in.getJarFile().getInputStream(entry))), name, parseResult, type);
			} else {
				switch (type) {
				case STARTS_WITH :
					if (entryName.startsWith(name) && !entryName.endsWith("/")) {
						parseResult.put (entryName, in.getJarFile().getInputStream (entry));
					}
				case ENDS_WITH :
					if (entryName.endsWith(name) && !entryName.endsWith("/")) {
						parseResult.put (entryName, in.getJarFile().getInputStream (entry));
					}
				case CONTAINS :
					if (entryName.indexOf(name) >= 0 && !entryName.endsWith("/")) {
						parseResult.put (entryName, in.getJarFile().getInputStream (entry));
					}
				default :	
					continue;
				}
			}
			in.getZis ().closeEntry();
		}
	}
	
	public int getResultCount () {
		return parseResult.size();
	}

	public Iterator getNames () {
		if (getResultCount () == 0) {
			return null;
		}
		return parseResult.keySet ().iterator ();
	}

	public InputStream getResurce (String name) {
		return (InputStream)parseResult.get (name);
	}
	
	public void addArchive (File archive) throws IOException {
		archives.add(new Archive (archive));
	}

	public void addArchive (String archive) throws IOException {
		archives.add (new Archive (archive));
	}

	public void addArchive (Class clazz) throws IOException, BaseException {
		URL rootUrl = Thread.currentThread().getContextClassLoader().getResource (StringUtils.replace (clazz.getName(), ".", "/") + ".class");
		String filePath = rootUrl.getPath();
		if (filePath.startsWith (FILE_URL_PREFIX) && filePath.indexOf (JAR_URL_SEPARATOR) > 0) {
			String jarFile = filePath.substring (FILE_URL_PREFIX.length (), filePath.indexOf(JAR_URL_SEPARATOR));
			archives.add (new Archive (jarFile));
		} else {
			throw new BaseException (clazz + " must be loaded from a jar file");
		}
	}

	protected Archive getArchive (int index) throws FileNotFoundException {
		return (Archive)archives.get(index);
	}

	protected void getArchivesCount () throws FileNotFoundException {
		archives.size();
	}

	public void reset () throws FileNotFoundException {
		archives.clear();
		parseResult.clear();
	}
	
	class Archive {
		private File file;
		private JarFile jarFile;
		private ZipInputStream zis;
		
		Archive (JarFile jarFile, ZipInputStream zis) throws IOException {
			this.jarFile = jarFile;
			this.zis = zis;
		}
		
		Archive (String archive) throws IOException {
			this (new File (archive));
		}
		
		Archive (File archive) throws IOException {
			file = archive;
			jarFile = new JarFile (file);
			zis = new ZipInputStream(new FileInputStream(file));
		}
		
		public File getFile() {
			return file;
		}
		public void setFile(File file) {
			this.file = file;
		}
		public JarFile getJarFile() {
			return jarFile;
		}
		public void setJarFile(JarFile jarFile) {
			this.jarFile = jarFile;
		}
		public ZipInputStream getZis() {
			return zis;
		}
		public void setZis(ZipInputStream zis) {
			this.zis = zis;
		}
		
	}
}